<?php
include_once 'lib/Aspect/Aspect.php';
include_once 'lib/Aspect/JointPoint.php';
include_once 'lib/Http/Session.php';
include_once 'lib/Http/Request.php';
include_once 'lib/Http/Response.php';
include_once 'lib/Http/Cookie.php';
include_once 'lib/Http/Session.php';
include_once 'lib/Router.php';
include_once 'lib/AOP.php';
include_once 'lib/Interceptors.php';
include_once 'lib/Debug.php';
include_once 'lib/Router.php';


class Application
{
    private string $staticPath = '/static';
    private string $projectPath = '';
    private array $getRouters;
    private array $postRouters;
    private array $aspects;
    private array $interceptors;
    /**
     *
     * @var PDO|null 数据库连接
     */
    private ?PDO $pdo = null;

    private ?Interceptor $staticInterceptor = null;

    private Request $request;
    private Response $response;


    /**
     * Application constructor.
     * @param Request $req 请求
     * @param Response $response 响应
     * @param bool $startSession 是否启动会话
     */
    public function __construct(Request $req, Response $response, bool $startSession = true)
    {

        if (session_status() == PHP_SESSION_NONE) {
            // 如果会话未启动，启动会话
            session_start();
        }
        $this->projectPath = dirname(__DIR__) . "/";
        $this->request = $req;
        $this->response = $response;
        $router = new Router();
        $aop = new AOP();
        $interceptor = new Interceptors();
        $this->getRouters = $router->getGetRouter();
        $this->postRouters = $router->getPostRouter();
        $this->aspects = $aop->getAspects();
        $this->interceptors = $interceptor->getInterceptors();
    }

    public function run()
    {
        // 请求分发器
        $this->dispatcher();
        // 输出响应
        $this->response->send();
    }

    private function dispatcher()
    {
        $uri = $this->request->getUri();
        if ($uri == null) {
            Debug::error("No found uri");
            return;
        }

        // 如果是favicon.ico，去static目录下找
        if ($uri == "/favicon.ico"){
            $uri = $this->staticPath . "/favicon.ico";
        }

        // 拦截所有php文件
        // 如果是静态资源文件，不包括php文件
        if ($this->isStaticResource($uri)) {

            if ($this->staticInterceptor != null) {
                // 执行拦截器
                if (!$this->staticInterceptor->preHandle($this->request, $this->response)) {
                    return;
                }
            }
            // 检查文件是否存在
            if (!file_exists($this->projectPath . $uri)) {
                $this->response->setStatusCode(404);
                return;
            }
            // 设置响应头
            $this->response->setContentType(explode(",",$this->request->getAccept())[0]);
            $this->response->setBody(file_get_contents("." . $uri));
            return;
        }


        // 构造第一优先级的通用Uri,某个路径下所有
        $firstUri = substr($uri, 0, strrpos($uri, "/")) . "/*";
        // 构造第二优先级的通用Uri,所有路径下所有
        $secondUri = "/**";


        // 获取handler处理对象
        $handlerPath = null;
        // 如果是post请求获取post中的路由对象
        if ($this->request->isPOSTMethod()) {
            // 获取handler处理对象
            $handlerPath = Utils::ifNull(@$this->postRouters[$uri], @$this->postRouters[$firstUri], @$this->postRouters[$secondUri]);
        }else{
            // 获取handler处理对象
            $handlerPath = Utils::ifNull(@$this->getRouters[$uri], @$this->getRouters[$firstUri], @$this->getRouters[$secondUri]);
        }


        // 如果没有找到handler处理对象
        if ($handlerPath == null) {
            // 没有找到handler处理对象
            Debug::error("No found handler: uri= " . $uri);
            return;
        }

        // 拦截器
        try {
            $interceptorRes = $this->applyInterceptor($handlerPath);
            if ($interceptorRes === false) {
                // 拦截器返回false
                $this->response->setStatusCode(500);
                return;
            }
        } catch (Exception $e) {
            Debug::error($e->getMessage());
            return;
        }

        // 将handlerPath解析为类名和方法名
        $className = explode("@", $handlerPath)[0];
        $methodName = explode("@", $handlerPath)[1];

        // 处理解析失败的情况
        if ($className == null || $methodName == null) {
            Debug::error("No found " . $className ." controller method: name= " . $methodName);
            return;
        }


        try {

            $handlerClass = new ReflectionClass($className);
            $handlerMethod = $handlerClass->getMethod($methodName);

            // 获取构造函数的参数
            $controllerArgc = $this->getControllerArgc($handlerClass);
            if (gettype($controllerArgc) === "NULL") {
                return;
            }
            // 获取实例
            $classInstance = $handlerClass->newInstance($controllerArgc);

            // 获取方法参数
            $methodArgc = $this->getMethodArgc($handlerMethod);
            if (gettype($methodArgc) === "NULL") {
                return;
            }

            //创建切入点
            $jointPoint = new JointPoint($classInstance, $handlerMethod, $methodArgc);
            //织入
            $value = $this->applyAOP($handlerPath, $jointPoint);
            $this->response->setBody($value);
        } catch (Exception $e) {
            Debug::error("Server error: " . $e->getMessage());
        }
    }

    /**
     * 静态资源分发器
     * @param Interceptor $staticInterceptor
     * @return void
     */
    public function setStaticInterceptor(Interceptor $staticInterceptor): void
    {
        $this->staticInterceptor = $staticInterceptor;
    }

    /**
     * 判断请求的是否是静态资源(以static开头且不是php文件)
     * .pdf：PDF文件
     * .txt：纯文本文件
     * .xml：XML文件
     * .json：JSON文件
     * .mp3：音频文件
     * .mp4：视频文件
     * .woff、.woff2：Web字体文件
     * .tiff：TIFF图像文件
     * .bmp：BMP图像文件
     * .webp：WebP图像文件
     * @param string $uri
     * @return bool
     */
    private function isStaticResource(string $uri): bool
    {
        return Utils::String_startWith($uri, $this->staticPath) && (
                Utils::String_endWith($uri, ".html") ||
                Utils::String_endWith($uri, ".vue") ||
                Utils::String_endWith($uri, ".css") ||
                Utils::String_endWith($uri, ".js") ||
                Utils::String_endWith($uri, ".jpg") ||
                Utils::String_endWith($uri, ".png") ||
                Utils::String_endWith($uri, ".gif") ||
                Utils::String_endWith($uri, ".jpeg") ||
                Utils::String_endWith($uri, ".ico") ||
                Utils::String_endWith($uri, ".svg") ||
                Utils::String_endWith($uri, ".pdf") ||
                Utils::String_endWith($uri, ".txt") ||
                Utils::String_endWith($uri, ".xml") ||
                Utils::String_endWith($uri, ".json") ||
                Utils::String_endWith($uri, ".mp3") ||
                Utils::String_endWith($uri, ".mp4") ||
                Utils::String_endWith($uri, ".woff") ||
                Utils::String_endWith($uri, ".woff2") ||
                Utils::String_endWith($uri, ".tiff") ||
                Utils::String_endWith($uri, ".bmp") ||
                Utils::String_endWith($uri, ".webp")
            );
    }


    public function setDataSources(string $dsn,string $username,string $password,array $options = []): void
    {
        $this->pdo = new PDO($dsn,$username,$password,$options);
    }

    /**
     * 拦截器
     * @param ?string $handlerPath 拦截器路径，这个路径都是通用的
     * @return bool
     */
    private function applyInterceptor(string $handlerPath): bool
    {
        $classAndMethod = explode("@", $handlerPath);

        $paths = [
            $classAndMethod[0] . "@" . $classAndMethod[1],//具体类
            $classAndMethod[0] . "@*",//类中的所有方法
            "*@*",//所有类所有方法
        ];
        foreach ($paths as $path) {
            if (!isset($this->interceptors[$path])) {
                continue;
            }
            $interceptor = $this->interceptors[$path];
            /** @var Interceptor $interceptor */

            // 找到织入
            if ($interceptor != null) {
                if (count($classAndMethod) < 2) {
                    throw new Exception("Interceptor path format error: " . $handlerPath);
                } else {
                    // 如果拦截器执行结果不为true
                    if (!$interceptor->preHandle($this->request, $this->response)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * AOP
     * @param string $handlerPath 控制器路径
     * @param JointPoint $jointPoint 切入点
     * @return mixed 返回值
     * @throws Exception 异常
     */
    private function applyAOP(string $handlerPath, JointPoint $jointPoint)
    {
        $classAndMethod = explode("@", $handlerPath);

        $paths = [
            $classAndMethod[0] . "@" . $classAndMethod[1],//具体类
            $classAndMethod[0] . "@*",//类中的所有方法
            "*@*",//所有类所有方法
        ];

        foreach ($paths as $path) {
            if (!isset($this->aspects[$path])) {
                continue;
            }
            $aspect = $this->aspects[$path];
            /** @var Aspect $aspect */

            // 找到织入
            if ($aspect != null) {
                // 前置
                $aspect->before();
                // 返回结果
                $value = '';
                try {
                    //环绕
                    $value = $aspect->around($jointPoint);
                    //后置
                    $aspect->after();
                } catch (Exception $e) {
                    // 发生异常
                    $isHandle = $aspect->exception($e, $jointPoint->getArgc(), $handlerPath);

                    // 处理异常了
                    if ($isHandle) {
                        $value = null;
                    } else {
                        throw $e;
                    }
                }
                //最终
                $aspect->finally();
                return $value;
            }
        }
        return null;
    }

    /**
     * 获取方法参数
     * 按照类型获取参数
     * @param ReflectionMethod $method
     * @return array|null
     */
    private function getMethodArgc(ReflectionMethod $method): ?array
    {
        $argc = [];
        $params = $method->getParameters();
        foreach ($params as $param) {
            $typeName = $param->getType()->getName();
            $paramName = $param->getName();
            switch ($typeName) {
                case 'Request':
                    $argc[] = $this->request;
                    break;
                case 'Response':
                    $argc[] = $this->response;
                    break;
                case 'Session':
                    $argc[] = $this->request->getSession();
                    break;
                case 'PDO':
                    if ($this->pdo == null) {
                        Debug::error("No found PDO instance");
                    }else{
                        $argc[] = $this->pdo;
                        break;
                    }
                case 'string' || 'int' || 'float' || 'double' || 'bool' || 'array' || 'object':
                    $nameValue = $this->request->getParameter($paramName);
                    if (gettype($nameValue) === $typeName && $nameValue != null) {
                        $argc[] = $nameValue;
                        break;
                    }
                default:
                    Debug::error("No found param instance: " . $typeName . " " . $paramName);
                    return null;
            }
        }
        if (count($argc) !== count($params)){
            return null;
        }
        return $argc;
    }

    private function getControllerArgc(ReflectionClass $class): ?array{
        $argc = [];
        $params = $class->getConstructor()->getParameters();
        foreach ($params as $param) {
            // 获取参数类型
            $typeName = $param->getType()->getName();
            // 获取参数名
            $paramName = $param->getName();

            // 先根据参数名获取参数值
            switch ($typeName) {
                case 'Request':
                    $argc[] = $this->request;
                    break;
                case 'Response':
                    $argc[] = $this->response;
                    break;
                case 'Session':
                    $argc[] = $this->request->getSession();
                    break;
                case 'PDO':
                    if ($this->pdo == null) {
                        Debug::error("No found PDO instance");
                    }else{
                        $argc[] = $this->pdo;
                        break;
                    }
                default:
                    Debug::error("No found param instance: " . $typeName . " " . $paramName);
                    return null;
            }
        }
        if (count($argc) !== count($params)){
            return null;
        }
        return $argc;
    }
}